// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/dom_storage/dom_storage_context_impl.h"

#include <inttypes.h>
#include <stddef.h>
#include <stdlib.h>

#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/guid.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/time/time.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/process_memory_dump.h"
#include "content/browser/dom_storage/dom_storage_area.h"
#include "content/browser/dom_storage/dom_storage_database.h"
#include "content/browser/dom_storage/dom_storage_namespace.h"
#include "content/browser/dom_storage/dom_storage_task_runner.h"
#include "content/browser/dom_storage/session_storage_database.h"
#include "content/common/dom_storage/dom_storage_types.h"
#include "content/public/browser/dom_storage_context.h"
#include "content/public/browser/local_storage_usage_info.h"
#include "content/public/browser/session_storage_usage_info.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace content {
namespace {

// Limits on the cache size and number of areas in memory, over which the areas
// are purged.
#if defined(OS_ANDROID)
    const unsigned kMaxStorageAreaCount = 20;
    const size_t kMaxCacheSize = 2 * 1024 * 1024;
#else
    const unsigned kMaxStorageAreaCount = 100;
    const size_t kMaxCacheSize = 20 * 1024 * 1024;
#endif

    const int kSessionStoraceScavengingSeconds = 60;

    // Offset the session storage namespace ids generated by different contexts
    // to help identify when an id from one is mistakenly used in another.
    int g_session_id_offset_sequence = 1;
    const int kSessionIdOffsetAmount = 10000;

    // Aggregates statistics from all the namespaces.
    DOMStorageNamespace::UsageStatistics GetTotalNamespaceStatistics(
        const DOMStorageContextImpl::StorageNamespaceMap& namespace_map)
    {
        DOMStorageNamespace::UsageStatistics total_stats = { 0 };
        for (const auto& it : namespace_map) {
            DOMStorageNamespace::UsageStatistics stats = it.second->GetUsageStatistics();
            total_stats.total_cache_size += stats.total_cache_size;
            total_stats.total_area_count += stats.total_area_count;
            total_stats.inactive_area_count += stats.inactive_area_count;
        }
        return total_stats;
    }

} // namespace

DOMStorageContextImpl::DOMStorageContextImpl(
    const base::FilePath& localstorage_directory,
    const base::FilePath& sessionstorage_directory,
    storage::SpecialStoragePolicy* special_storage_policy,
    scoped_refptr<DOMStorageTaskRunner> task_runner)
    : localstorage_directory_(localstorage_directory)
    , sessionstorage_directory_(sessionstorage_directory)
    , task_runner_(std::move(task_runner))
    , session_id_offset_(abs((g_session_id_offset_sequence++ % 10)) * kSessionIdOffsetAmount)
    , session_id_sequence_(session_id_offset_)
    , is_shutdown_(false)
    , force_keep_session_state_(false)
    , special_storage_policy_(special_storage_policy)
    , scavenging_started_(false)
    , is_low_end_device_(base::SysInfo::IsLowEndDevice())
{
    // Tests may run without task runners.
    if (task_runner_) {
        // Registering dump provider is safe even outside the task runner.
        base::trace_event::MemoryDumpManager::GetInstance()
            ->RegisterDumpProviderWithSequencedTaskRunner(
                this, "DOMStorage", task_runner_->GetSequencedTaskRunner(DOMStorageTaskRunner::PRIMARY_SEQUENCE),
                base::trace_event::MemoryDumpProvider::Options());
    }
}

DOMStorageContextImpl::~DOMStorageContextImpl()
{
    DCHECK(is_shutdown_);
    if (session_storage_database_.get()) {
        // SessionStorageDatabase shouldn't be deleted right away: deleting it will
        // potentially involve waiting in leveldb::DBImpl::~DBImpl, and waiting
        // shouldn't happen on this thread.
        SessionStorageDatabase* to_release = session_storage_database_.get();
        to_release->AddRef();
        session_storage_database_ = NULL;
        task_runner_->PostShutdownBlockingTask(
            FROM_HERE,
            DOMStorageTaskRunner::COMMIT_SEQUENCE,
            base::Bind(&SessionStorageDatabase::Release,
                base::Unretained(to_release)));
    }
}

DOMStorageNamespace* DOMStorageContextImpl::GetStorageNamespace(
    int64_t namespace_id)
{
    if (is_shutdown_)
        return NULL;
    StorageNamespaceMap::iterator found = namespaces_.find(namespace_id);
    if (found == namespaces_.end()) {
        if (namespace_id == kLocalStorageNamespaceId) {
            if (!localstorage_directory_.empty()) {
                if (!base::CreateDirectory(localstorage_directory_)) {
                    LOG(ERROR) << "Failed to create 'Local Storage' directory,"
                                  " falling back to in-memory only.";
                    localstorage_directory_ = base::FilePath();
                }
            }
            DOMStorageNamespace* local = new DOMStorageNamespace(localstorage_directory_, task_runner_.get());
            namespaces_[kLocalStorageNamespaceId] = local;
            return local;
        }
        return NULL;
    }
    return found->second.get();
}

void DOMStorageContextImpl::GetLocalStorageUsage(
    std::vector<LocalStorageUsageInfo>* infos,
    bool include_file_info)
{
    if (localstorage_directory_.empty()) {
        DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId);
        std::vector<GURL> origins;
        local->GetOriginsWithAreas(&origins);
        for (const GURL& origin : origins) {
            LocalStorageUsageInfo info;
            info.origin = origin;
            infos->push_back(info);
        }
        return;
    }

    base::FileEnumerator enumerator(localstorage_directory_, false,
        base::FileEnumerator::FILES);
    for (base::FilePath path = enumerator.Next(); !path.empty();
         path = enumerator.Next()) {
        if (path.MatchesExtension(DOMStorageArea::kDatabaseFileExtension)) {
            LocalStorageUsageInfo info;
            info.origin = DOMStorageArea::OriginFromDatabaseFileName(path);
            if (include_file_info) {
                base::FileEnumerator::FileInfo find_info = enumerator.GetInfo();
                info.data_size = find_info.GetSize();
                info.last_modified = find_info.GetLastModifiedTime();
            }
            infos->push_back(info);
        }
    }
}

void DOMStorageContextImpl::GetSessionStorageUsage(
    std::vector<SessionStorageUsageInfo>* infos)
{
    if (!session_storage_database_.get()) {
        for (const auto& entry : namespaces_) {
            std::vector<GURL> origins;
            entry.second->GetOriginsWithAreas(&origins);
            for (const GURL& origin : origins) {
                SessionStorageUsageInfo info;
                info.persistent_namespace_id = entry.second->persistent_namespace_id();
                info.origin = origin;
                infos->push_back(info);
            }
        }
        return;
    }

    std::map<std::string, std::vector<GURL>> namespaces_and_origins;
    session_storage_database_->ReadNamespacesAndOrigins(
        &namespaces_and_origins);
    for (std::map<std::string, std::vector<GURL>>::const_iterator it = namespaces_and_origins.begin();
         it != namespaces_and_origins.end(); ++it) {
        for (std::vector<GURL>::const_iterator origin_it = it->second.begin();
             origin_it != it->second.end(); ++origin_it) {
            SessionStorageUsageInfo info;
            info.persistent_namespace_id = it->first;
            info.origin = *origin_it;
            infos->push_back(info);
        }
    }
}

void DOMStorageContextImpl::DeleteLocalStorageForPhysicalOrigin(
    const GURL& origin_url)
{
    DCHECK(!is_shutdown_);
    url::Origin origin(origin_url);
    DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId);
    std::vector<GURL> origins;
    local->GetOriginsWithAreas(&origins);
    // Suborigin at the physical origin of |origin_url| should have their storage
    // deleted as well.
    // https://w3c.github.io/webappsec-suborigins/
    for (const auto& origin_candidate_url : origins) {
        url::Origin origin_candidate(origin_candidate_url);
        // |origin| is guaranteed to be deleted below, so don't delete it until
        // then. That is, only suborigins at the same physical origin as |origin|
        // should be deleted at this point.
        if (!origin_candidate.IsSameOriginWith(origin) && origin_candidate.IsSamePhysicalOriginWith(origin)) {
            DeleteLocalStorage(origin_candidate_url);
        }
    }
    // It is important to always explicitly delete |origin|. If it does not appear
    // it |origins| above, there still may be a directory open in the namespace in
    // preparation for this storage, and this call will make sure that the
    // directory is deleted.
    DeleteLocalStorage(origin_url);
}

void DOMStorageContextImpl::DeleteLocalStorage(const GURL& origin_url)
{
    DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId);
    local->DeleteLocalStorageOrigin(origin_url);
    // Synthesize a 'cleared' event if the area is open so CachedAreas in
    // renderers get emptied out too.
    DOMStorageArea* area = local->GetOpenStorageArea(origin_url);
    if (area)
        NotifyAreaCleared(area, origin_url);
}

void DOMStorageContextImpl::DeleteSessionStorage(
    const SessionStorageUsageInfo& usage_info)
{
    DCHECK(!is_shutdown_);
    DOMStorageNamespace* dom_storage_namespace = NULL;
    std::map<std::string, int64_t>::const_iterator it = persistent_namespace_id_to_namespace_id_.find(
        usage_info.persistent_namespace_id);
    if (it != persistent_namespace_id_to_namespace_id_.end()) {
        dom_storage_namespace = GetStorageNamespace(it->second);
    } else {
        int64_t namespace_id = AllocateSessionId();
        CreateSessionNamespace(namespace_id, usage_info.persistent_namespace_id);
        dom_storage_namespace = GetStorageNamespace(namespace_id);
    }
    dom_storage_namespace->DeleteSessionStorageOrigin(usage_info.origin);
    // Synthesize a 'cleared' event if the area is open so CachedAreas in
    // renderers get emptied out too.
    DOMStorageArea* area = dom_storage_namespace->GetOpenStorageArea(usage_info.origin);
    if (area)
        NotifyAreaCleared(area, usage_info.origin);
}

void DOMStorageContextImpl::Flush()
{
    for (auto& entry : namespaces_)
        entry.second->Flush();
}

void DOMStorageContextImpl::Shutdown()
{
    if (task_runner_)
        task_runner_->AssertIsRunningOnPrimarySequence();
    is_shutdown_ = true;
    StorageNamespaceMap::const_iterator it = namespaces_.begin();
    for (; it != namespaces_.end(); ++it)
        it->second->Shutdown();

    base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
        this);

    if (localstorage_directory_.empty() && !session_storage_database_.get())
        return;

    // Respect the content policy settings about what to
    // keep and what to discard.
    if (force_keep_session_state_)
        return; // Keep everything.

    bool has_session_only_origins = special_storage_policy_.get() && special_storage_policy_->HasSessionOnlyOrigins();

    if (has_session_only_origins) {
        // We may have to delete something. We continue on the
        // commit sequence after area shutdown tasks have cycled
        // thru that sequence (and closed their database files).
        bool success = task_runner_->PostShutdownBlockingTask(
            FROM_HERE,
            DOMStorageTaskRunner::COMMIT_SEQUENCE,
            base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins, this));
        DCHECK(success);
    }
}

void DOMStorageContextImpl::AddEventObserver(EventObserver* observer)
{
    event_observers_.AddObserver(observer);
}

void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer)
{
    event_observers_.RemoveObserver(observer);
}

void DOMStorageContextImpl::NotifyItemSet(
    const DOMStorageArea* area,
    const base::string16& key,
    const base::string16& new_value,
    const base::NullableString16& old_value,
    const GURL& page_url)
{
    for (auto& observer : event_observers_)
        observer.OnDOMStorageItemSet(area, key, new_value, old_value, page_url);
}

void DOMStorageContextImpl::NotifyItemRemoved(
    const DOMStorageArea* area,
    const base::string16& key,
    const base::string16& old_value,
    const GURL& page_url)
{
    for (auto& observer : event_observers_)
        observer.OnDOMStorageItemRemoved(area, key, old_value, page_url);
}

void DOMStorageContextImpl::NotifyAreaCleared(
    const DOMStorageArea* area,
    const GURL& page_url)
{
    for (auto& observer : event_observers_)
        observer.OnDOMStorageAreaCleared(area, page_url);
}

int64_t DOMStorageContextImpl::AllocateSessionId()
{
    return base::subtle::Barrier_AtomicIncrement(&session_id_sequence_, 1);
}

std::string DOMStorageContextImpl::AllocatePersistentSessionId()
{
    std::string guid = base::GenerateGUID();
    std::replace(guid.begin(), guid.end(), '-', '_');
    return guid;
}

// Used to diagnose unknown namespace_ids given to the ipc message filter.
base::Optional<bad_message::BadMessageReason>
DOMStorageContextImpl::DiagnoseSessionNamespaceId(int64_t namespace_id)
{
    if (namespace_id < session_id_offset_ || namespace_id - session_id_offset_ > kSessionIdOffsetAmount) {
        return bad_message::DSH_WRONG_STORAGE_PARTITION;
    }

    if (base::ContainsValue(recently_deleted_session_ids_, namespace_id))
        return bad_message::DSH_DELETED_SESSION_ID;

    int last_allocated_session_id = base::subtle::Barrier_AtomicIncrement(&session_id_sequence_, 0);
    if (namespace_id <= last_allocated_session_id)
        return bad_message::DSH_NOT_CREATED_SESSION_ID;
    return bad_message::DSH_NOT_ALLOCATED_SESSION_ID;
}

void DOMStorageContextImpl::CreateSessionNamespace(
    int64_t namespace_id,
    const std::string& persistent_namespace_id)
{
    if (is_shutdown_)
        return;
    DCHECK(namespace_id != kLocalStorageNamespaceId);
    DCHECK(namespaces_.find(namespace_id) == namespaces_.end());
    namespaces_[namespace_id] = new DOMStorageNamespace(
        namespace_id, persistent_namespace_id, session_storage_database_.get(),
        task_runner_.get());
    persistent_namespace_id_to_namespace_id_[persistent_namespace_id] = namespace_id;
}

void DOMStorageContextImpl::DeleteSessionNamespace(int64_t namespace_id,
    bool should_persist_data)
{
    DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
    StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
    if (it == namespaces_.end())
        return;
    std::string persistent_namespace_id = it->second->persistent_namespace_id();
    if (session_storage_database_.get()) {
        if (!should_persist_data) {
            task_runner_->PostShutdownBlockingTask(
                FROM_HERE,
                DOMStorageTaskRunner::COMMIT_SEQUENCE,
                base::Bind(
                    base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace),
                    session_storage_database_,
                    persistent_namespace_id));
        } else {
            // Ensure that the data gets committed before we shut down.
            it->second->Shutdown();
            if (!scavenging_started_) {
                // Protect the persistent namespace ID from scavenging.
                protected_persistent_session_ids_.insert(persistent_namespace_id);
            }
        }
    }
    persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id);
    namespaces_.erase(namespace_id);

    recently_deleted_session_ids_.push_back(namespace_id);
    if (recently_deleted_session_ids_.size() > 10)
        recently_deleted_session_ids_.pop_front();
}

void DOMStorageContextImpl::CloneSessionNamespace(
    int64_t existing_id,
    int64_t new_id,
    const std::string& new_persistent_id)
{
    if (is_shutdown_)
        return;
    DCHECK_NE(kLocalStorageNamespaceId, existing_id);
    DCHECK_NE(kLocalStorageNamespaceId, new_id);
    StorageNamespaceMap::iterator found = namespaces_.find(existing_id);
    if (found != namespaces_.end()) {
        namespaces_[new_id] = found->second->Clone(new_id, new_persistent_id);
        return;
    }
    CreateSessionNamespace(new_id, new_persistent_id);
}

void DOMStorageContextImpl::ClearSessionOnlyOrigins()
{
    if (!localstorage_directory_.empty()) {
        std::vector<LocalStorageUsageInfo> infos;
        const bool kDontIncludeFileInfo = false;
        GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
        for (size_t i = 0; i < infos.size(); ++i) {
            const GURL& origin = infos[i].origin;
            if (special_storage_policy_->IsStorageProtected(origin))
                continue;
            if (!special_storage_policy_->IsStorageSessionOnly(origin))
                continue;

            base::FilePath database_file_path = localstorage_directory_.Append(
                DOMStorageArea::DatabaseFileNameFromOrigin(origin));
            sql::Connection::Delete(database_file_path);
        }
    }
    if (session_storage_database_.get()) {
        std::vector<SessionStorageUsageInfo> infos;
        GetSessionStorageUsage(&infos);
        for (size_t i = 0; i < infos.size(); ++i) {
            const GURL& origin = infos[i].origin;
            if (special_storage_policy_->IsStorageProtected(origin))
                continue;
            if (!special_storage_policy_->IsStorageSessionOnly(origin))
                continue;
            session_storage_database_->DeleteArea(infos[i].persistent_namespace_id,
                origin);
        }
    }
}

void DOMStorageContextImpl::SetSaveSessionStorageOnDisk()
{
    DCHECK(namespaces_.empty());
    if (!sessionstorage_directory_.empty()) {
        session_storage_database_ = new SessionStorageDatabase(
            sessionstorage_directory_);
    }
}

void DOMStorageContextImpl::StartScavengingUnusedSessionStorage()
{
    if (session_storage_database_.get()) {
        task_runner_->PostDelayedTask(
            FROM_HERE, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces, this),
            base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
    }
}

void DOMStorageContextImpl::PurgeMemory(PurgeOption purge_option)
{
    if (is_shutdown_)
        return;

    DOMStorageNamespace::UsageStatistics initial_stats = GetTotalNamespaceStatistics(namespaces_);
    if (!initial_stats.total_area_count)
        return;

    // Track the total localStorage cache size.
    UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.BrowserLocalStorageCacheSizeInKB",
        initial_stats.total_cache_size / 1024, 1, 100000,
        50);

    const char* purge_reason = nullptr;
    if (purge_option == PURGE_IF_NEEDED) {
        // Purging is done based on the cache sizes without including the database
        // size since it can be expensive trying to estimate the sqlite usage for
        // all databases. For low end devices purge all inactive areas.
        if (initial_stats.total_cache_size > kMaxCacheSize)
            purge_reason = "SizeLimitExceeded";
        else if (initial_stats.total_area_count > kMaxStorageAreaCount)
            purge_reason = "AreaCountLimitExceeded";
        else if (is_low_end_device_)
            purge_reason = "InactiveOnLowEndDevice";
        if (!purge_reason)
            return;

        purge_option = PURGE_UNOPENED;
    } else {
        if (purge_option == PURGE_AGGRESSIVE)
            purge_reason = "AggressivePurgeTriggered";
        else
            purge_reason = "ModeratePurgeTriggered";
    }

    // Return if no areas can be purged with the given option.
    bool aggressively = purge_option == PURGE_AGGRESSIVE;
    if (!aggressively && !initial_stats.inactive_area_count) {
        return;
    }
    for (const auto& it : namespaces_)
        it.second->PurgeMemory(aggressively);

    // Track the size of cache purged.
    size_t purged_size_kib = (initial_stats.total_cache_size - GetTotalNamespaceStatistics(namespaces_).total_cache_size) / 1024;
    std::string full_histogram_name = std::string("LocalStorage.BrowserLocalStorageCachePurgedInKB.") + purge_reason;
    base::HistogramBase* histogram = base::Histogram::FactoryGet(
        full_histogram_name, 1, 100000, 50,
        base::HistogramBase::kUmaTargetedHistogramFlag);
    if (histogram)
        histogram->Add(purged_size_kib);
    UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.BrowserLocalStorageCachePurgedInKB",
        purged_size_kib, 1, 100000, 50);
}

bool DOMStorageContextImpl::OnMemoryDump(
    const base::trace_event::MemoryDumpArgs& args,
    base::trace_event::ProcessMemoryDump* pmd)
{
    if (session_storage_database_)
        session_storage_database_->OnMemoryDump(pmd);
    if (args.level_of_detail == base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND) {
        DOMStorageNamespace::UsageStatistics total_stats = GetTotalNamespaceStatistics(namespaces_);
        auto* mad = pmd->CreateAllocatorDump(
            base::StringPrintf("dom_storage/0x%" PRIXPTR "/cache_size",
                reinterpret_cast<uintptr_t>(this)));
        mad->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
            base::trace_event::MemoryAllocatorDump::kUnitsBytes,
            total_stats.total_cache_size);
        mad->AddScalar("inactive_areas",
            base::trace_event::MemoryAllocatorDump::kUnitsObjects,
            total_stats.inactive_area_count);
        mad->AddScalar("total_areas",
            base::trace_event::MemoryAllocatorDump::kUnitsObjects,
            total_stats.total_area_count);
        return true;
    }
    for (const auto& it : namespaces_) {
        it.second->OnMemoryDump(pmd);
    }
    return true;
}

void DOMStorageContextImpl::FindUnusedNamespaces()
{
    DCHECK(session_storage_database_.get());
    if (scavenging_started_)
        return;
    scavenging_started_ = true;
    std::set<std::string> namespace_ids_in_use;
    for (StorageNamespaceMap::const_iterator it = namespaces_.begin();
         it != namespaces_.end(); ++it)
        namespace_ids_in_use.insert(it->second->persistent_namespace_id());
    std::set<std::string> protected_persistent_session_ids;
    protected_persistent_session_ids.swap(protected_persistent_session_ids_);
    task_runner_->PostShutdownBlockingTask(
        FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
        base::Bind(
            &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence,
            this, namespace_ids_in_use, protected_persistent_session_ids));
}

void DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence(
    const std::set<std::string>& namespace_ids_in_use,
    const std::set<std::string>& protected_persistent_session_ids)
{
    DCHECK(session_storage_database_.get());
    // Delete all namespaces which don't have an associated DOMStorageNamespace
    // alive.
    std::map<std::string, std::vector<GURL>> namespaces_and_origins;
    session_storage_database_->ReadNamespacesAndOrigins(&namespaces_and_origins);
    for (std::map<std::string, std::vector<GURL>>::const_iterator it = namespaces_and_origins.begin();
         it != namespaces_and_origins.end(); ++it) {
        if (namespace_ids_in_use.find(it->first) == namespace_ids_in_use.end() && protected_persistent_session_ids.find(it->first) == protected_persistent_session_ids.end()) {
            deletable_persistent_namespace_ids_.push_back(it->first);
        }
    }
    if (!deletable_persistent_namespace_ids_.empty()) {
        task_runner_->PostDelayedTask(
            FROM_HERE, base::Bind(&DOMStorageContextImpl::DeleteNextUnusedNamespace, this),
            base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
    }
}

void DOMStorageContextImpl::DeleteNextUnusedNamespace()
{
    if (is_shutdown_)
        return;
    task_runner_->PostShutdownBlockingTask(
        FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
        base::Bind(
            &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence,
            this));
}

void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence()
{
    if (deletable_persistent_namespace_ids_.empty())
        return;
    const std::string& persistent_id = deletable_persistent_namespace_ids_.back();
    session_storage_database_->DeleteNamespace(persistent_id);
    deletable_persistent_namespace_ids_.pop_back();
    if (!deletable_persistent_namespace_ids_.empty()) {
        task_runner_->PostDelayedTask(
            FROM_HERE, base::Bind(&DOMStorageContextImpl::DeleteNextUnusedNamespace, this),
            base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
    }
}

} // namespace content
